package cn.iocoder.yudao.framework.crypto.core;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.exception.Assertions;
import cn.iocoder.yudao.framework.common.exception.KnownServiceException;
import cn.iocoder.yudao.framework.crypto.annotation.ApiRequestDecrypt;
import cn.iocoder.yudao.framework.crypto.pojo.CryptCommonForm;
import cn.iocoder.yudao.framework.crypto.pojo.CryptoProperties;
import jakarta.annotation.Nonnull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

/**
 * <pre>
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 *  请求参数加密 Advice
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 * </pre>
 *
 * @link <a href="https://gitee.com/lab1024/smart-admin/blob/master/smart-admin-api-java17-springboot3/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/DecryptRequestAdvice.java">DecryptRequestAdvice.java</a>
 * @author 山野羡民（1032694760@qq.com）
 * @since 2025/04/03
 */
@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
public class ApiDecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {
    private final CryptoProperties cryptoProperties;

    @Override
    public boolean supports(@Nonnull MethodParameter methodParameter, @Nonnull Type targetType, @Nonnull Class<? extends HttpMessageConverter<?>> converterType) {
        boolean support = false;
        if (cryptoProperties.isEnable()) {
            // 功能启用
            if (methodParameter.hasMethodAnnotation(ApiRequestDecrypt.class)) {
                // 方法加了 @ApiRequestDecrypt 注解
                support = true;
            } else if (methodParameter.hasParameterAnnotation(ApiRequestDecrypt.class)) {
                // 参数加了 @ApiRequestDecrypt 注解
                support = true;
            } else if (methodParameter.getContainingClass().isAnnotationPresent(ApiRequestDecrypt.class)) {
                // 类或子类加了 @ApiRequestDecrypt 注解
                support = true;
            }
        }
        if (support && cryptoProperties.isShowLog()) {
            log.debug("[supports][请求参数加密 Advice 拦截处理] methodParameter={}, targetType={}", methodParameter, targetType);
        }
        return support;
    }

    @Nonnull
    @Override
    public HttpInputMessage beforeBodyRead(@Nonnull HttpInputMessage inputMessage, @Nonnull MethodParameter parameter, @Nonnull Type targetType, @Nonnull Class<? extends HttpMessageConverter<?>> converterType) {
        try {
            String bodyStr = IoUtil.read(inputMessage.getBody(), StandardCharsets.UTF_8);
            if (StrUtil.isBlank(bodyStr)) {
                return inputMessage;
            }
            // 获取客户端 AES 加密密钥
            String encryptedAesKey = inputMessage.getHeaders().getFirst(cryptoProperties.getAesHeader());
            Assertions.notBlank(encryptedAesKey, StrUtil.format("请求头 {} 为空！", cryptoProperties.getAesHeader()));
            // 通过 RSA 私钥解密 AES 加密密钥
            byte[] decryptedAesKey = SecureUtil.rsa(cryptoProperties.getPrivateKey(), cryptoProperties.getPublicKey()).decrypt(encryptedAesKey, KeyType.PrivateKey);
            // 通过解密后的 AES 密钥解密数据
            String encryptedData = JSONUtil.parseObj(bodyStr).toBean(CryptCommonForm.class).getData();
            byte[] decryptedData = SecureUtil.aes(decryptedAesKey).decrypt(encryptedData);
            if (cryptoProperties.isShowLog()) {
                log.info("[beforeBodyRead] 接收到的加密数据：{}，解密后：{}", encryptedData, new String(decryptedData, StandardCharsets.UTF_8));
            }
            return new DecryptHttpInputMessage(inputMessage.getHeaders(), IoUtil.toStream(decryptedData));
        } catch (Exception e) {
            if (cryptoProperties.isShowLog()) {
                log.error("[beforeBodyRead] 请求参数解密 Advice 拦截处理出错", e);
            }
            throw new KnownServiceException("请求参数解密异常");
        }
    }

    @Nonnull
    @Override
    public Object afterBodyRead(@Nonnull Object body, @Nonnull HttpInputMessage inputMessage, @Nonnull MethodParameter parameter, @Nonnull Type targetType, @Nonnull Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, @Nonnull HttpInputMessage inputMessage, @Nonnull MethodParameter parameter, @Nonnull Type targetType, @Nonnull Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    public record DecryptHttpInputMessage(HttpHeaders headers, InputStream body) implements HttpInputMessage {

        @Nonnull
        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }

        @Nonnull
        @Override
        public InputStream getBody() {
            return body;
        }

    }

}
